/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <baja/app/input.hpp>
#include <baja/assert.hpp>
#include <journal/app/trace_tags.h>
#include <journal/common/memory_tracking_service.h>
#include <journal/app/constants.h>


namespace journal { namespace services { namespace memory_tracking {

namespace b = baja;
namespace g = baja::graphics;
namespace a = baja::app;

const b::float32 memory_tracking_service::_margin = 4.f;
const b::float32 memory_tracking_service::_graphicHeight = 40.f;
const b::float32 memory_tracking_service::_graphicWidth = 420.f;
const b::float32 memory_tracking_service::_labelHeight = 20.f;
const b::uint32 memory_tracking_service::_backdropAlpha = 196;

const b::uint32 memory_tracking_service::_baseInternalTypeToken = 1000;


memory_tracking_service::data::data(): 
    buffer(constants::performance::sampleCount()), 
    lastResetMs(0.0), 
    dirty(false), 
    lastNonZero(0), 
    lastNonZeroAvg(0), 
    init(false) 
{}

memory_tracking_service::memory_tracking_service() :
    _firstRender(true),
    _internalTypeToken(_baseInternalTypeToken)
{
}

std::shared_ptr<memory_tracking_service> memory_tracking_service::create(const std::shared_ptr<g::iengine>& graphics)
{
    std::shared_ptr<memory_tracking_service> returnValue = std::make_shared<memory_tracking_service>();
    returnValue->init(graphics);
    return returnValue;
}

void memory_tracking_service::init(const std::shared_ptr<g::iengine>& graphics)
{
    g::viewport vp;
    graphics->getViewport(&vp);
    a::resize_args args;
    args.width = (b::int32)vp.width;
    args.height = (b::int32)vp.height;
    onResize(args);
}

void memory_tracking_service::registerType(
    const baja::uint32& typeToken,
    const std::wstring& name,
    const b::Boolean& cumulative
    )
{
    BAJA_LOCK(_mutex);

    BAJA_ASSERT(typeToken < _baseInternalTypeToken, L"type token is higher than allowed value");

    type_info typeInfo;
    typeInfo.typeName = name;
    typeInfo.instanceCount = 0;
    typeInfo.sizeInBytes = 0;
    typeInfo.cumulative = cumulative;
    typeInfo._data = std::make_shared<data>();

    _memoryMap[typeToken] = typeInfo;
}

baja::uint32 memory_tracking_service::registerType(
    const std::wstring& name,
    const b::Boolean& cumulative
    )
{
    BAJA_LOCK(_mutex);

    type_info typeInfo;
    typeInfo.typeName = name;
    typeInfo.instanceCount = 0;
    typeInfo.sizeInBytes = 0;
    typeInfo.cumulative = cumulative;
    typeInfo._data = std::make_shared<data>();

    _memoryMap[_internalTypeToken] = typeInfo;

    return _internalTypeToken++;
}

void memory_tracking_service::logCreate(
    const baja::uint32& typeToken, 
    const baja::uint32& id,
    const size_t& sizeInBytes
    )
{
    BAJA_LOCK(_mutex);

    _memoryMap[typeToken].instanceCount++;
    if (_memoryMap[typeToken].cumulative)
    {
        _memoryMap[typeToken].sizeInBytes += sizeInBytes;
    }
    else
    {
        _memoryMap[typeToken].sizeInBytes = sizeInBytes;
    }
    collect(typeToken, sizeInBytes);
}

void memory_tracking_service::logExpand(
    const baja::uint32& typeToken, 
    const baja::uint32& id,
    const size_t& sizeInBytes
    )
{
    BAJA_LOCK(_mutex);

    if (_memoryMap[typeToken].cumulative)
    {
        _memoryMap[typeToken].sizeInBytes += sizeInBytes;
    }
    else
    {
        _memoryMap[typeToken].sizeInBytes = sizeInBytes;
    }
    collect(typeToken, sizeInBytes);
}

void memory_tracking_service::logShrink(
    const baja::uint32& typeToken, 
    const baja::uint32& id,
    const size_t& sizeInBytes
    )
{
    BAJA_LOCK(_mutex);

    if (_memoryMap[typeToken].cumulative)
    {
        _memoryMap[typeToken].sizeInBytes -= sizeInBytes;
    }
    else
    {
        _memoryMap[typeToken].sizeInBytes = sizeInBytes;
    }
    uncollect(typeToken, sizeInBytes);
}

void memory_tracking_service::logDispose(
    const baja::uint32& typeToken, 
    const baja::uint32& id,
    const size_t& sizeInBytes
    )
{
    BAJA_LOCK(_mutex);

    _memoryMap[typeToken].instanceCount--;
    if (_memoryMap[typeToken].cumulative)
    {
        _memoryMap[typeToken].sizeInBytes -= sizeInBytes;
    }
    else
    {
        _memoryMap[typeToken].sizeInBytes = sizeInBytes;
    }
    uncollect(typeToken, sizeInBytes);
}

std::shared_ptr<g::ifont> memory_tracking_service::getFont()
{
    _font = constants::graphics()->getAssetManager()->getOrCreateFont(
        constants::graphics(), 
        constants::references::fontName(), 
        constants::references::fontSizeSmall(),
        false // right to left
        );

    return _font;
}

void memory_tracking_service::collect(const b::uint32 typeToken, const size_t sizeInBytes)
{
    data& d = *(_memoryMap[typeToken]._data);
    if (!d.init)
    {
        // initialize it the first time around
        reset(typeToken);
        d.init = true;
    }

    BAJA_LOCK(d._mutex);
    BAJA_VERIFY(d.lastResetMs > 0);

    d.dirty = true;
    if (_memoryMap[typeToken].cumulative)
    {
        d.buffer.current()->value += static_cast<double>(sizeInBytes);
    }
    else
    {
        d.buffer.current()->value = static_cast<double>(sizeInBytes);
    }
    d.buffer.current()->count++;

    if (sizeInBytes != 0) d.lastNonZero = static_cast<double>(sizeInBytes);
}

void memory_tracking_service::reset(const b::uint32 typeToken, bool restart)
{
    data& d = *(_memoryMap[typeToken]._data);
    BAJA_LOCK(d._mutex);

    size_t count = 0; 
    double value = 0;

    if (d.init)
    {
        count = d.buffer.current()->count;
        value = d.buffer.current()->value;
    }

    if (restart) d.buffer.reset();
    d.buffer.add();
    d.buffer.current()->count = count;
    d.buffer.current()->value = value;
    d.lastResetMs = _sw.getElapsedMilliseconds();
    d.dirty = false;
}

void memory_tracking_service::uncollect(const b::uint32 typeToken, const size_t sizeInBytes)
{
    data& d = *(_memoryMap[typeToken]._data);
    if (!d.init)
    {
        // initialize it the first time around
        reset(typeToken);
        d.init = true;
    }

    BAJA_LOCK(d._mutex);
    BAJA_VERIFY(d.lastResetMs > 0);

    d.dirty = true;
    if (_memoryMap[typeToken].cumulative)
    {
        d.buffer.current()->value -= sizeInBytes;
    }
    else
    {
        d.buffer.current()->value = static_cast<double>(sizeInBytes);
    }
    d.buffer.current()->count--;

    if (sizeInBytes != 0) d.lastNonZero = static_cast<double>(sizeInBytes);
}

void memory_tracking_service::calculatePositions(
    const int slot,
    const b::uint32 e,
    const b::wchar* topLabel, 
    const b::wchar* bottomLabel, 
    const b::float32 previousTop,
    m::rectangle* topLabelRect,
    m::rectangle* bottomLabelRect,
    m::rectangle* graphicRect,
    m::rectangle* totalRect
    )
{
    b::float32 graphicHeight = _graphicHeight; // _desc[(int)e].showGraph ? _graphicHeight : 0;
    b::float32 graphicMargins = _margin*2; // _desc[(int)e].showGraph ? _margin*2 : 0;

    b::float32 slotTop = previousTop - (graphicHeight + _labelHeight*2 + _margin*2 + graphicMargins);

    b::float32 slotLeft = _graphicWidth + _margin*2;
    slotLeft = _vp.right - slotLeft;

    totalRect->top = slotTop;
    totalRect->left = slotLeft;
    totalRect->bottom = slotTop + (graphicHeight + _labelHeight*2 + _margin*2 + graphicMargins);
    totalRect->right = slotLeft + _graphicWidth + _margin*2;

    // graphic
    graphicRect->top = slotTop + _labelHeight + _margin *2;
    graphicRect->left = slotLeft + _margin;
    graphicRect->bottom = graphicRect->top + graphicHeight;
    graphicRect->right = totalRect->right - _margin;

    // top label
    this->getFont()->measureText(
        topLabel,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        false,
        topLabelRect
        );

    topLabelRect->left = slotLeft + _margin;
    topLabelRect->top = totalRect->top + _margin;
    topLabelRect->right += topLabelRect->left;
    topLabelRect->bottom += bottomLabelRect->top;

    // bottom label
    this->getFont()->measureText(
        bottomLabel,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        false,
        bottomLabelRect
        );

    bottomLabelRect->left = slotLeft + _margin;
    bottomLabelRect->top = totalRect->bottom - _margin - _labelHeight;;
    bottomLabelRect->right += bottomLabelRect->left;
    bottomLabelRect->bottom += bottomLabelRect->top;
}


void memory_tracking_service::onResize(const a::resize_args& args)
{
    _vp.left = 0;
    _vp.top = 0;
    _vp.right = (b::float32)args.width;
    _vp.bottom = (b::float32)args.height;
}

void memory_tracking_service::resetAll(bool restart)
{
    _lastResetMs = _sw.getElapsedMilliseconds();

    for (auto &it : _memoryMap)
    {
        reset(it.first, restart);
    }
}

void memory_tracking_service::onRender()
{
    if (_firstRender)
    {
        this->resetAll(true);
        _firstRender = false;
    }

    double now = _sw.getElapsedMilliseconds();
    if (now - _lastResetMs >= 1000.0)
    {
        this->resetAll();
    }

    if (!constants::memory_tracking_settings::showMonitors())
    {
        return;
    }

    b::float32 previousTop = _vp.bottom - 150; // leave some space for axes

    for (auto &it : _memoryMap)
    {
        std::wstring label = 
                (boost::wformat(L"%s : %i objects : %i bytes") %
                    it.second.typeName %
                    it.second.instanceCount %
                    it.second.sizeInBytes).str();
        renderSlot(it.first, label, it.second.instanceCount, it.second.sizeInBytes, previousTop);
    }
}

void memory_tracking_service::get(
    const b::uint32 typeToken,
    b::float32* count,
    b::float32* value
    )
{
    data& d = *(_memoryMap[typeToken]._data);
    BAJA_LOCK(d._mutex);

    if (d.buffer.length() <= 1)
    {
        *count = *value = -1.f;
    }
    else
    {
        *count = static_cast<b::float32>(d.buffer.previous()->count);
        *value = static_cast<b::float32>(d.buffer.previous()->value);
    }
}

void memory_tracking_service::renderSlot(
    const b::uint32 typeToken,
    const std::wstring& name,
    const size_t& count,
    const size_t& sizeInBytes,
    b::float32& previousTop)
{
    std::shared_ptr<g::iengine> graphics = constants::graphics();

    m::rectangle topLabelRect;
    m::rectangle bottomLabelRect;
    m::rectangle graphicRect;
    m::rectangle totalRect;

    this->calculatePositions(
        0, // slot : no op
        0, // event : no op
        name.c_str(),
        name.c_str(),
        previousTop,
        &topLabelRect, 
        &bottomLabelRect, 
        &graphicRect, 
        &totalRect
        );

    // draw total backdrop rectangle
    std::shared_ptr<g::ieffect2> effect;
    effect = graphics->getStandardEffect(g::standard_effect::screenColored);

    effect->begin(graphics);
    graphics->setAlphaBlend(g::enabled_state::enabled);
    graphics->setZBuffer(g::enabled_state::disabled);
    graphics->setCulling(g::cull_state::none);

    graphics->beginScreenSpace(g::primitive_type::triangleStrip);

    graphics->setColor(g::packColor(memory_tracking_service::_backdropAlpha, 0, 0, 0));

    m::point4d pos1(totalRect.left, totalRect.top, 1.0f);
    graphics->setScreenSpaceVertex(pos1);

    m::point4d pos2(totalRect.right-1, totalRect.top, 1.0f);
    graphics->setScreenSpaceVertex(pos2);

    graphics->setColor(g::packColor(memory_tracking_service::_backdropAlpha, 0, 0, 0));

    m::point4d pos4(totalRect.left, totalRect.bottom-1, 1.0f);
    graphics->setScreenSpaceVertex(pos4);

    m::point4d pos3(totalRect.right-1, totalRect.bottom-1, 1.0f);
    graphics->setScreenSpaceVertex(pos3);

    graphics->end();

    effect->end(graphics);

    // draw the top label
    static const g::packed_color textColor = g::packColor(255,198,198,198);
    this->getFont()->renderText(
        graphics,
        name.c_str(),
        textColor,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        &topLabelRect
        );

    // draw the bottom label
    this->getFont()->renderText(
        graphics,
        L"",
        textColor,
        g::horizontal_alignment::left,
        g::vertical_alignment::top,
        &bottomLabelRect
        );

    renderGraphic(typeToken, typeToken, &graphicRect);

    previousTop = totalRect.top - 5; // add some margin
}

void memory_tracking_service::renderGraphic(const b::uint32 slot, const b::uint32 typeToken, const m::rectangle* rect)
{
    std::shared_ptr<g::iengine> graphics = constants::graphics();

    // render axes
    graphics->setAlphaBlend(g::enabled_state::enabled);
    graphics->setZBuffer(g::enabled_state::disabled);
    graphics->setCulling(g::cull_state::none);

    graphics->beginScreenSpace(g::primitive_type::lineStrip);

    g::packed_color color = g::packColor(255,198,198,198);

    graphics->setColor(color);

    m::point4d pos1(rect->left, rect->top, 1.0f);
    graphics->setScreenSpaceVertex(pos1);

    m::point4d pos2(rect->left, rect->bottom, 1.0f);
    graphics->setScreenSpaceVertex(pos2);

    m::point4d pos3(rect->right, rect->bottom, 1.0f);
    graphics->setScreenSpaceVertex(pos3);

    graphics->end();

    // render graph
    // first find max
    data& d = *(_memoryMap[typeToken]._data);
    if (d.buffer.length() <= 1) { return; }

    double maxval = -FLT_MAX;
    for (int i = 0; i < d.buffer.length(); i++)
    {
        data_point* cur = d.buffer.at(i);
        if (cur->value > maxval) maxval = cur->value;
    }

    graphics->beginScreenSpace(g::primitive_type::triangleStrip);

    g::packed_color c = g::packColor(
        64,
        255,
        255,
        255);

    graphics->setColor(c);

    for (int i = 0; i < d.buffer.length() - 1; i++)
    {
        data_point* cur = d.buffer.at(i);

        m::point4d p = mapValue(cur->value, maxval, i, rect);
        graphics->setScreenSpaceVertex(p);
        p.y = rect->bottom;
        graphics->setScreenSpaceVertex(p);
    }

    graphics->end();

    graphics->beginScreenSpace(g::primitive_type::lineStrip);

    c = g::packColor(
        255,
        255,
        0,
        255);

    graphics->setColor(c);

    for (int i = 0; i < d.buffer.length() - 1; i++)
    {
        data_point* cur = d.buffer.at(i);

        graphics->setScreenSpaceVertex(mapValue(cur->value, maxval, i, rect));
    }

    graphics->end();
}

m::point4d memory_tracking_service::mapValue(double value, double max, int i, const m::rectangle* rect)
{
    m::point4d p;

    // normalize
    float f = static_cast<float>(value / max);
    f *= _graphicHeight;

    p.y = rect->bottom - f;
    p.x = rect->left + (_graphicWidth / constants::performance::sampleCount()) * i;
    p.z = 1.f;

    return p;
}

void memory_tracking_service::onScreenChange(const baja::app::screen_change_args& args)
{
    /* noop */
}

void memory_tracking_service::onVisibilityChanged(const bool visible)
{
    /* noop */
}

std::shared_ptr<imemory_tracking_service> journal::services::memory_tracking::createMemoryTrackingService(const std::shared_ptr<g::iengine>& graphics)
{
    std::shared_ptr<memory_tracking_service> returnValue;
    returnValue = memory_tracking_service::create(graphics);
    return std::dynamic_pointer_cast<imemory_tracking_service>(returnValue);
}

}}} // journal::services::memory_tracking